#ifndef SORTER_HPP_
#define SORTER_HPP_

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>

#include "Logger.hpp"
#include "Mapper.hpp"
#include "File.hpp"

class Sorter {
private:
	File mergedFile;
	std::vector<File> files;
	unsigned int nChunks;
	

	void prepareFiles() {
		unsigned int i;
		for (i = 0; i < nChunks; ++i) {
			files.push_back(File()); 
		}
		char buf[20];
		for (i = 0; i < nChunks; ++i) {
			snprintf(buf,20, "tmp%d", i+1);
			files[i].setPath(buf);
			files[i].Open(File::MODE_RW);
		}
		char buff[7];
		strncpy(buff,"tmpRes",7);
		mergedFile.setPath(buff);
		mergedFile.Open(File::MODE_RW);
	}

	void sort() {
		unsigned int i;
		for (i = 0; i < nChunks; ++i) {
			files[i].mSize = files[i].size;
			Mapper::map(files[i], Mapper::MODE_RW);
			qsort((int*)files[i].mPtr, files[i].size/sizeof(int), sizeof(int), compare);
			Mapper::unmap(files[i]);
		}	
	}
public:
	void merge() {
		unsigned int i;
		for (i = 0; i < nChunks; ++i) {
			files[i].cSize = 0;
			files[i].offset = 0;
		}
		mergedFile.cSize = 0;

		while (1) {
			for (i = 0; i < files.size(); ++i) {
				if (files[i].cSize == files[i].offset) {
					if (files[i].isMapped) {
						Mapper::unmap(files[i]);
					}
					files[i].mSize = std::min(pageSize*(chunkSize/files.size()+1),files[i].size - files[i].offset);//
					Mapper::map(files[i], Mapper::MODE_READ);
				}
			}
			if (mergedFile.cSize == mergedFile.offset) {
				if (mergedFile.isMapped) {
					Mapper::unmap(mergedFile);
				}
				mergedFile.mSize = pageSize*(chunkSize/(files.size()*pageSize)+1);
				Mapper::map(mergedFile, Mapper::MODE_WRITE);
			}

			unsigned int minIndex = min();
			*(int*)mergedFile.cPtr = *(int*)files[minIndex].cPtr;
			files[minIndex].cSize += sizeof(int);
			mergedFile.cSize += sizeof(int);
			
			if (files[minIndex].cSize == files[minIndex].size) {
				removeFile(minIndex);
			} else {
				files[minIndex].cPtr += 4;
			}
			
			if (0 == files.size()) {
				break;
			}
			mergedFile.cPtr += 4;
		}

		if (-1 == ftruncate(mergedFile.fd,mergedFile.cSize)) {	
			Logger::getInstance().log(Logger::ERROR, "Ftruncate of file *%s* failed.\n", mergedFile.path);
			exit(1);
		}

	}
private:
	void removeFile(unsigned int minIndex) {
		if (files[minIndex].isMapped) {
			Mapper::unmap(files[minIndex]);
		}
		if (files[minIndex].isOpened) {
			files[minIndex].Close();
		}
		if (unlink(files[minIndex].path) != 0) {
			Logger::getInstance().log(Logger::WARNING, "Failed to remove file *%s*.\n", files[minIndex].path);
		}
		files.erase(files.begin() + minIndex);
	}

	/*
	 * Returns index of file with minimum current value
	 */
	unsigned int min() {
		unsigned int minIndex = 0;
		unsigned int i;
		for (i = 1; i < files.size(); ++i) {
			if (*(int*)files[i].cPtr < *(int*)files[minIndex].cPtr) { 
				minIndex = i;
			}
		}
		return minIndex;
	}

	static int compare(const void *a, const void *b) {
		if (*(int*)a < 0 && *(int*)b > 0) {
			return -1;
		}
		if (*(int*)a > 0 && *(int*)b < 0) {
			return 1;
		}
		return ( *(int*)a - *(int*)b );
	}
public:
	Sorter(unsigned int n) {
		nChunks = n;
		files.reserve(nChunks);
	}

	~Sorter() {
		files.clear();
		if (mergedFile.isMapped) {
			Mapper::unmap(mergedFile);
		}
		if (mergedFile.isOpened) {
			mergedFile.Close();
		}
	}

	void sortAndMerge() {
		prepareFiles();
		sort();
		if (nChunks > 1)
			merge();

		if (mergedFile.isMapped) {
			Mapper::unmap(mergedFile);
		}
		if (mergedFile.isOpened) {
			mergedFile.Close();
		}
	}

};

#endif /* SORTER_HPP_ */
